package com.ks.winterwarmimage.esptouch;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import androidx.annotation.NonNull;

import com.espressif.iot.esptouch.EsptouchTask;
import com.espressif.iot.esptouch.IEsptouchResult;
import com.espressif.iot.esptouch2.provision.EspProvisioner;
import com.espressif.iot.esptouch2.provision.EspProvisioningListener;
import com.espressif.iot.esptouch2.provision.EspProvisioningRequest;
import com.espressif.iot.esptouch2.provision.EspProvisioningResult;
import com.espressif.iot.esptouch2.provision.EspSyncListener;
import com.espressif.iot.esptouch2.provision.TouchNetUtil;

import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * ESPTouch配置管理类
 */
public class EspTouchManage {
    private static final String TAG = "EspTouchManage";
    /** GB2312编码 */
    public static final Charset GB2312 = Charset.forName("GB2312");
    /** GBK编码 */
    public static final Charset GBK = Charset.forName("GBK");
    /**AES密钥*/
    public static final String DEFAULT_AES_KEY = "XMKSZNKJYXGS6688";
    /**设置应用程序上下文*/
    private Context context;
    /**EspTouch_v2配置类*/
    private EspProvisioner mProvisioner;
    /**EspTouch_v2配置请求类*/
    private EspProvisioningRequest provisionerRequest;
    /**EspTouch配置任务类*/
    private EsptouchTask mEspTouchTask;
    /**是否v2版本*/
    private boolean isEspTouch_v2;
    /**WiFi名称*/
    private String ssid;
    /**WiFi Mac地址*/
    private String bssid;
    /**WiFi密码*/
    private String password;
    /**设备数量*/
    private int deviceCount=1;
    private OnProvisioningListener pListener;
    /**配置返回列表*/
    private List<ProvisioningResult> provisioningResults=new ArrayList<>();

    /**
     * 构造函数
     * @param context 上下文
     */
    public EspTouchManage(Context context, boolean v2) {
        this(context,null,null,null,v2);
    }
    /**
     * 构造函数
     * @param context 上下文
     * @param ssid 路由器SSID（热点名称）
     * @param bssid 路由器BSSID
     * @param password 热点密码
     * @param v2 是否为EspTouch_v2版本
     */
    public EspTouchManage(Context context, String ssid, String bssid, String password, boolean v2) {
        this.context = context.getApplicationContext();   //得到应用程序上下文
        this.ssid = ssid;
        this.bssid = bssid;
        this.password = password;
        this.isEspTouch_v2 = v2;
        if(isEspTouch_v2)
            mProvisioner = new EspProvisioner(context);
    }
    public void setVersion(boolean v2){
        this.isEspTouch_v2 = v2;
        release();
        if(isEspTouch_v2) {
            mProvisioner = new EspProvisioner(context);
        }
    }
    public void set(String ssid,String bssid,String password){
        this.context = context.getApplicationContext();   //得到应用程序上下文
        this.ssid = ssid;
        this.bssid = bssid;
        this.password = password;
    }

    /**
     * 设置配置侦听器
     * @param provisioningListener 配置侦听器
     */
    public void setProvisioningListener(OnProvisioningListener provisioningListener) {
        this.pListener = provisioningListener;
    }

    /**
     * 开始同步，具体作用不太清楚，个人认为是用于将手机的网络信息与模
     * 块的网络信息进行匹配，以达到更好的实现配网（EspTouch_v2版有效）
     */
    public void startSync(){
        if(mProvisioner!=null)
            mProvisioner.startSync(syncListener);
    }

    /**
     * 停止同步包（EspTouch_v2版有效）
     */
    public void stopSync(){
        if(mProvisioner!=null)
            if(mProvisioner.isSyncing())
                mProvisioner.stopSync();
    }
    /**
     * 开始配置
     * @param deviceCount 设备数量
     * @param broadcast 如果为true，则发送广播数据包，否则发送多播数据包
     */
    public void startProvisioning(int deviceCount,boolean broadcast){
        if(!isEspTouch_v2) {
            //EspTouch_v1版本，创建线程来执行配网任务
            new Thread(() -> {
                mEspTouchTask = new EsptouchTask(ssid.getBytes(StandardCharsets.UTF_8),
                        TouchNetUtil.convertBssid2Bytes(bssid),
                        password.getBytes(StandardCharsets.UTF_8), context);
                mEspTouchTask.setPackageBroadcast(broadcast);        //设置广播还是组播
                handler.sendEmptyMessage(MSG_PROVISIONING_START);     //配置开始
                List<IEsptouchResult> results = mEspTouchTask.executeForResults(deviceCount);  //执行配网任务并等待配网返回结果
                if (results == null) {
                    Log.e(TAG, "ProvisionListener: results is null");
                    //发送消息
                    Exception e = new Exception("Create EspTouch task failed, the EspTouch port could be used by other thread");
                    Message message=Message.obtain();
                    message.what = MSG_PROVISIONING_FAILURE;
                    message.obj=e;
                    handler.sendMessage(message);
                    return;
                }
                IEsptouchResult first = results.get(0);
                //检查任务是否已取消且未收到任何结果
                if(!first.isCancelled()){
                    Log.e(TAG, "ProvisionListener: User cancel the task");
                }
                //任务在收到足够的结果之前收到了一些结果，包括在执行过程中被取消
                if (first.isSuc()) {
                    Log.e(TAG, "ProvisionListener: provisioning success");
                    for (IEsptouchResult result : results) {
                        String mac = result.getBssid();
                        String host = result.getInetAddress().getHostAddress();
                        Log.e(TAG, "ProvisionListener onResponse: " + mac + " " + host);
                        ProvisioningResult pResult = new ProvisioningResult(result.getInetAddress(),result.getBssid());
                        provisioningResults.add(pResult);
                    }
                    //发送消息
                    handler.sendEmptyMessage(MSG_PROVISIONING_SUCCESS);
                    stopProvisioning();
                    handler.sendEmptyMessageAtTime(MSG_PROVISIONING_STOP,100);  //发送停止消息
                }else{
                    Log.e(TAG, "ProvisionListener: provisioning failed");
                    //发送消息
                    Exception e = new Exception("EspTouch failed");
                    Message message=Message.obtain();
                    message.what = MSG_PROVISIONING_FAILURE;
                    message.obj=e;
                    handler.sendMessage(message);
                }
            }).start();
        }
    }
    /**
     * 开始预配置（使用默认密钥进行配置）
     * @param deviceCount 设备数量
     * @param customData 用户自定义数据
     */
    public void startProvisioning(int deviceCount,String customData){
        startProvisioning(deviceCount,customData,DEFAULT_AES_KEY);
    }

    /**
     * 开始配网（V2版）
     * @param accountID 需要绑定到设备的账户ID,用来进行同步设备到第三方手机（固定8个字符）
     * @param devName 设备注册的名称（最大25个字符）
     */
    public void startProvisioning(String accountID,String devName){
        if(accountID==null||accountID.getBytes(GB2312).length!=8){
            throw new IllegalArgumentException("accountID length must be 8");
        }
        if(devName.getBytes(GB2312).length>25){
            throw new IllegalArgumentException("groupName length must be less than 25");
        }
        //通过自定义数据将账户ID和组名称传递给设备进行存储，当一个为注册设备的手机同步不实际时可以将这些数据读取出来写入本地数据库
        String userData=accountID+devName;
        //startProvisioning(1,userData,DEFAULT_AES_KEY);
        startProvisioning(1,userData);
    }
    /**
     * 开始预配置
     * @param deviceCount 设备数量
     * @param customData 用户自定义数据
     * @param aesKey AES密钥，如果为null，则不加密
     */
    public void startProvisioning(int deviceCount,String customData,String aesKey){
        if(isEspTouch_v2) {
//            mProvisioner = new EspProvisioner(context);
            ByteBuffer  buffer = ByteBuffer.allocate(64);
            //customData="4378ADEcTestData";
            if(customData != null) {
                buffer.put(customData.getBytes(GB2312));
            }
            if(this.bssid!=null && this.bssid.length()>=17){
                buffer.position(64-6);
                buffer.put(TouchNetUtil.convertBssid2Bytes(this.bssid));
            }
            provisionerRequest = new EspProvisioningRequest.Builder(this.context)
                    // AP的SSID，可空
                    .setSSID(this.ssid.getBytes(StandardCharsets.UTF_8))
                    // AP的BSSID, 可空
                    .setBSSID(TouchNetUtil.convertBssid2Bytes(this.bssid))
                    // AP的密码，如果AP处于打开状态，则可以为空
                    .setPassword(this.password.getBytes())
                    // 用户的自定义数据，可以为空。如果不为空，则最大长度为64
                    //.setReservedData(customData != null ? customData.getBytes(GB2312) : null)
                    .setReservedData(buffer.array())
                    // 可以为null，如果不是null，则必须为16个字节。应用程序开发人员应首先与设备开发人员协商AES密钥。
                    .setAESKey(aesKey.getBytes())
                    .build();
            if (deviceCount <= 0) {
                deviceCount = 1;
            }
            Log.e(TAG, "BSSID: " + this.bssid);
            this.deviceCount = deviceCount;
            if (mProvisioner.isSyncing())
                stopSync();
            if (mProvisioner.isProvisioning())
                mProvisioner.stopProvisioning();
            mProvisioner.startProvisioning(provisionerRequest, provisioningListener); // 请求为非null，侦听器为null
        }
    }

    /**
     * 停止配置
     */
    public void stopProvisioning(){
        if(isEspTouch_v2){
            if(mProvisioner!=null)
                mProvisioner.stopProvisioning();
        }else {
            if(mEspTouchTask!=null)
                mEspTouchTask.interrupt();
        }
    }

    /**
     * 释放资源。调用这个函数会同步调用stopProvisioning的任务
     */
    public void release(){
        if(isEspTouch_v2){
            if(mProvisioner!=null)
                mProvisioner.close();
            mProvisioner=null;
        }else {
            if(mEspTouchTask!=null)
                mEspTouchTask.interrupt();
            mEspTouchTask=null;
        }
    }
    /**
     * 同步侦听器(V2)
     */
    private EspSyncListener syncListener=new EspSyncListener() {
        @Override
        public void onStart() {
            Log.e(TAG, "SyncListener onStart");
        }
        @Override
        public void onStop() {
            Log.e(TAG, "SyncListener onStop");
        }
        @Override
        public void onError(Exception e) {
            e.printStackTrace();
            if (mProvisioner != null) {
                mProvisioner.stopSync();
            }
        }
    };
    /**
     * 配置侦听器(V2)
     */
    EspProvisioningListener provisioningListener = new EspProvisioningListener() {
        @Override
        public void onStart() {
            Log.e(TAG, "ProvisionListener onStart: ");
            handler.sendEmptyMessage(MSG_PROVISIONING_START);
        }
        @Override
        public void onResponse(EspProvisioningResult result) {
            String mac = result.bssid;
            String host = result.address.getHostAddress();
            Log.e(TAG, "ProvisionListener onResponse: " + mac + " " + host);
            //在这里通过触发次数是否大于deviceCount来确定是否停止配网
            if(deviceCount>0){
                provisioningResults.add(new ProvisioningResult(result.address,result.bssid));
                deviceCount--;
            }
            //待所有请求的配置都完成后才发送消息到主线程
            if(deviceCount<=0){
                handler.sendEmptyMessage(MSG_PROVISIONING_SUCCESS);
                //停止配置
                stopProvisioning();
            }
        }
        @Override
        public void onStop() {
            if(deviceCount<=0){    //是否为正常停止
                Log.e(TAG, "ProvisionListener onStop: complete");
                handler.sendEmptyMessage(MSG_PROVISIONING_STOP);
            }else{
                Log.e(TAG, "ProvisionListener onStop: fail");
                Exception e=new Exception("Provisioning timeout");
                Message message=Message.obtain();
                message.what = MSG_PROVISIONING_FAILURE;
                message.obj=e;
                handler.sendMessage(message);
            }
        }
        @Override
        public void onError(Exception e) {
            Log.w(TAG, "ProvisionListener onError: ", e);
            Message message=Message.obtain();
            message.what = MSG_PROVISIONING_FAILURE;
            message.obj=e;
            handler.sendMessage(message);
        }
    };
    /**配置开始*/
    private static final int MSG_PROVISIONING_START=1;
    /**配置停止*/
    private static final int MSG_PROVISIONING_STOP=2;
    /**配置成功*/
    private static final int MSG_PROVISIONING_SUCCESS=3;
    /**配置失败*/
    private static final int MSG_PROVISIONING_FAILURE=4;
    Handler handler=new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message message) {
            if(pListener!=null){
                switch (message.what){
                    case MSG_PROVISIONING_START:
                        pListener.onStart();
                        break;
                    case MSG_PROVISIONING_STOP:
                        pListener.onStop();
                        break;
                    case MSG_PROVISIONING_SUCCESS:
                        pListener.onSuccess(provisioningResults);
                        break;
                    case MSG_PROVISIONING_FAILURE:
                        pListener.onFailure((Exception) message.obj);
                        break;
                }
            }
            return false;
        }
    });
    /**
     * 配置返回结果
     */
    public static class ProvisioningResult{
        /**路由器分配给设备的地址*/
        public final InetAddress address;
        /**设备mac地址*/
        public final String bssid;

        /**
         * 实例化
         * @param address 路由器分配给设备的地址
         * @param bssid 设备mac地址。16进制字符串，如"00:00:00:00:00:00"
         */
       public ProvisioningResult(InetAddress address, String bssid) {
            this.address = address;
            this.bssid = bssid;
        }
    }
    /**
     * 配置回调接口
     */
    public interface OnProvisioningListener{
        /**
         * 配置开始
         */
        void onStart();
        /**
         * 配置成功
         * @param results 配置成功结果
         */
        void onSuccess(List<ProvisioningResult> results);
        /**
         * 配置失败
         * @param e 失败原因
         */
        void onFailure(Exception e);
        /**
         * 配置结束
         */
        void onStop();
    }
}
